#include<stdio.h>
#include<unistd.h>
#include<assert.h>
#include<string.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<stdlib.h>

#define MAX 1024
#define ARGC 64
#define SEP " "

enum redir
{
  REDIR_INPUT=0,
  REDIR_OUTPUT,
  REDIR_APPEND,
  REDIR_NONE
};


int split(char* commandstr,char* argv[])//进行字符串切割
{
  assert(commandstr);
  assert(argv);

  //第一次调用时，strtok()必需给予参数s字符串
  //往后的调用则将参数s设置成NULL。每次调用成功则返回指向被分割出片段的指针
  argv[0]=strtok(commandstr,SEP);
  if(argv[0]==NULL) return -1;
  int i=1;
  while((argv[i++]=strtok(NULL,SEP)));
  //while(1)
  //{
  //  argv[i]=strtok(NULL,SEP);
  //  if(argv[i]==NULL) break;
  //  i++;
  //}
  return 0;
}

void debugPrint(char* argv[])
{
  int i=0;
  for(i=0;argv[i];i++)
  {
    printf("%d:%s\n",i,argv[i]);
  }
}

void showEnv()
{
  extern char**environ;//environ是一个外部的全局变量，存储着系统的环境变量
  int i=0;
  for(i=0;environ[i];i++)
  {
    printf("%d:%s\n",i,environ[i]);
  }
}

int main()
{
  char myenv[32][256];//用于存储环境变量
  int env_index=0;
  int last_exit=0;

  while(1)
  {
    char commandstr[MAX]={0};
    char* argv[ARGC]={NULL};
    printf("[rxy@my_machine path]# ");
    char *s=fgets(commandstr,sizeof(commandstr)-1,stdin);
    assert(s);
    (void)s;//保证在release方式使用时，由于断言导致没有使用而带来编译报警。这条语句并无实际用处，只是充当一次使用
    commandstr[strlen(commandstr)-1]='\0';//避免由于输入完命令后按下回车导致的换行，这里使用\0替换\n
    //printf("%s\n",commandstr);
    
    int n=split(commandstr,argv);//将一个字符串根据空格切割成多个
    if(n!=0) continue;//当n为0时对字符串的切割成功
    

    //执行cd cd..之类让bash执行的命令，这类命令称为内置命令
    //cd之类的命令不能由子进程执行
    if(strcmp(argv[0],"cd")==0)
    {
      if(argv[1]!=NULL)
      {
        chdir(argv[1]);
      }
      continue;
    }
    else if(strcmp(argv[0],"export")==0)//执行更改环境变量的操作 
    {
      if(argv[1]!=NULL)
      {
        //环境变量被导出的时候不能放在一个可以变化的字符串中
        //环境变量是一个表，表的内部是指针，不可以轻易改变
        strcpy(myenv[env_index],argv[1]);
        putenv(myenv[env_index++]);
        //不能是putenv(argv[1]),因为argv指向的是commandstr的内容，而用户在bash下不断输入指令
        //commandstr的内容不断更新，会导致argv的内容覆盖
        //一般用户自定义的环境变量，在bash中要用户自己来进行尾部，不要用一个经常被覆盖的缓冲区来保存环境变量
      }
      continue;
    }
    else if(strcmp(argv[0],"env")==0)
    //当我们在用env命令查看环境变量的时候，显示的是父进程bash的环境变量列表
    //所以需要重新实现env命令，使其显示当前进程的环境变量
    {
      showEnv();
      continue;
    }
    else if(strcmp(argv[0],"echo")==0)
    {
      const char *target_env=NULL;
      if(argv[1][0]=='$')//判断第一个命令的第一个字符是否是$
      {
        if(argv[1][1]=='?')
        {
          printf("%d\n",last_exit);//如果输入 echo $?则显示退出码
          continue;
        }
        else
        {
          target_env=getenv(argv[1]+1);//去除$
        }
        if(target_env!=NULL)//argv[1]为环境变量的名称，argv[1]+1是跳过变量名第一个$符号
        {
          printf("%s=%s\n",argv[1]+1,target_env);
        }
      }
      continue;
    }
    //在命令末尾位置替换为auto选项，给命令着色
    if(strcmp(argv[0],"ls")==0)
    {
      int pos=0;
      while(argv[pos])
      {
        pos++;
      }
      argv[pos++]=(char*)"--color=auto";
      argv[pos]=NULL;//目的是更加安全
    }


    pid_t id=fork();
    assert(id>=0);
    (void)id;
    if(id==0)
    {
      //子进程
      execvp(argv[0],argv);
      exit(-1);
    }
    int status=0;
    pid_t ret=waitpid(id,&status,0);
    if(ret>0)
    {
      last_exit=WEXITSTATUS(status);//如果返回值不为0，则返回退出码
    }
  }
}
